package com.trepadora.bean;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import org.ajax4jsf.event.PushEventListener;
import org.apache.log4j.Logger;

import com.fourspaces.couchdb.Database;
import com.fourspaces.couchdb.Document;
import com.fourspaces.couchdb.Session;
import com.fourspaces.couchdb.View;
import com.fourspaces.couchdb.ViewResults;
import com.trepadora.mapper.ChatMapper;
import com.trepadora.mapper.RoomMapper;
import com.trepadora.mapper.UserMapper;
import com.trepadora.model.Chat;
import com.trepadora.model.Room;
import com.trepadora.utils.ChatInterface;
import com.trepadora.utils.ListenerManager;

public class ChatBean implements ChatInterface {

	private static final Logger logger = Logger.getLogger(ChatBean.class);
	
	private String userId;
	private Boolean active;
	private String guestId;
	
	private String userPost;
	private String roomId;
	private boolean tempAccount;

	private String streamIn;
	private String streamOut;
	private String oldRoom;
	private boolean changeStreaming = false;
	private int streamCounter = 0;
	
	private String welcomeMsg;
	private String message;
	
	PushEventListener listener;

	// Mappers
	private final UserMapper userMapper = new UserMapper();
	private final RoomMapper roomMapper = new RoomMapper();
	private final ChatMapper chatMapper = new ChatMapper();

	/* Gets & Sets */
	public String getUserPost() {
		return userPost;
	}

	public void setUserPost(String userPost) {
		this.userPost = userPost;
	}

	public Boolean getActive() {
		return active;
	}

	public String getMessage() {
		return message;
	}
	
	public String getRoomId() {
		return roomId;
	}

	public String getWelcomeMsg() {
		return welcomeMsg;
	}

	public String getStreamIn() {
		return streamIn;
	}

	public String getStreamOut() {
		return streamOut;
	}
	
	
	public boolean isChangeStreaming() {
		return changeStreaming;
	}

	public void checkStreaming(){
		
		if(this.roomId == null || this.streamIn == null || this.streamIn.equals(EMPTY_STRING) || 
				this.streamOut == null || this.streamOut.equals(EMPTY_STRING)) {
			changeStreaming = true;
		} else {
			if(this.oldRoom != roomId) {
				this.oldRoom = this.roomId;
				changeStreaming = true;
				streamCounter = 0;
			} else {
				if(streamCounter < 2){
					changeStreaming = true;
					streamCounter += 1;
				} else
					changeStreaming = false;
			}
		}
	}

	/* Constructor */
	public ChatBean() {
		logger.trace("ChatBean constructor");
		
		this.active = false;
		this.guestId = null;
		this.userPost = EMPTY_STRING;
		this.roomId = null;
		this.message = EMPTY_STRING;	
		this.welcomeMsg = WELCOME_MSG;
		this.tempAccount = false;
		this.streamIn = null;
		this.streamOut = null;
		this.oldRoom = null;
	}

	/*
	 * Add new ajax listener (push)
	 */
	public void addListener(EventListener listener) {
		synchronized (listener) {
			this.listener = ListenerManager.getInstance().getListenerByUserId(
					this.userId);

			if (this.listener != listener) {
				ListenerManager.getInstance().addListenerToMap(this.userId,
						(PushEventListener) listener);
			}
		}
	}

	/**
	 * Register new temporary user.
	 */
	private void tempUserRegister() {
		
		try {
			
			this.userId = this.userMapper.saveTmpUser(getUserIp());
			logger.trace("New user come: " + this.userId);
			
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	/**
	 * Start game by user.
	 * 
	 */
	public void startGame() {
		logger.trace("Start game by user: " + this.userId);
		
		try {
			this.message = EMPTY_STRING;
			
			// Make user active
			this.active = this.userMapper.setActiveState(this.userId, true);
			
			// Make user ready to talk
			this.userMapper.setBusyState(this.userId, false);
			
			// Find free user to chat
			this.guestId = this.userMapper.findFreeUser(this.userId);
			
			// start chat with new user
			if (this.guestId != null && !this.guestId.equals("")) {

				// Make user busy
				this.userMapper.setBusyState(this.userId, true);

				// Create new chat room for 2 users
				this.streamIn = AUDIOVIDEO + UUID.randomUUID().toString();
				this.streamOut = AUDIOVIDEO + UUID.randomUUID().toString();
				this.roomId = roomMapper.saveRoom(this.userId, this.guestId, this.streamIn , this.streamOut, true);

				// Send control post to new chat room
				chatMapper.saveChat(this.roomId, this.userId, START_ROOM + UUID.randomUUID().toString());

				// Active users listener
				activeUserListener(this.userId);
				activeUserListener(this.guestId);

			} else {
				logger.trace("No free users to chat!");
				this.message = MSG_NO_USER;
			}
			
		} catch (IOException e) {
			logger.error(e);
			this.message = MSG_PROBLEMS;
		}
	}

	/*
	 * Stop game by user (Button 'Stop game')
	 */
	public void stopGame() {
		
		try {
			this.message = EMPTY_STRING;
			
			// Inactive state for user
			this.active = this.userMapper.setActiveState(this.userId, false);
			
			// Close chat room if not closed by other user
			if(this.roomId != null){
				
				// Send STOP control post to chat room
				chatMapper.saveChat(this.roomId, this.userId, STOP_ROOM + this.roomId);
				
				// Inactive room state
				this.roomMapper.setActiveState(this.roomId, false);
				
				// Active guest listener
				activeUserListener(this.guestId);
			}
			
			this.streamIn = AUDIOVIDEO + UUID.randomUUID().toString();
			this.streamOut = AUDIOVIDEO + UUID.randomUUID().toString();
			
			// Active user listener
			activeUserListener(this.userId);

		} catch (IOException e1) {
			logger.error(e1);
			this.message = MSG_PROBLEMS;
		}
	}
	
	/*
	 * Choose next player to game.
	 */
	public void nextPlayer() {
		
		this.message = EMPTY_STRING;
		
		// Close chat room if not closed by other user
		if(this.roomId != null){
			
			try {
				
				// Send STOP control post to chat room
				chatMapper.saveChat(this.roomId, this.userId, STOP_ROOM + this.roomId);
				
				// Inactive room state
				this.roomMapper.setActiveState(this.roomId, false);
				
				// Active users listener
				activeUserListener(this.guestId);
				
				// Clear chat parameters
				this.guestId = null;
				this.roomId = null;
				
				this.streamIn = EMPTY_STRING;
				this.streamOut = EMPTY_STRING;
		
			} catch (IOException e) {
				logger.error(e);
				this.message = MSG_PROBLEMS;
			}	
		}
		
		try {
			// Make user ready to talk
			this.userMapper.setBusyState(this.userId, false);
			
			// Find free user to chat
			this.guestId = this.userMapper.findFreeUser(this.userId);
			
			if (this.guestId != null && !this.guestId.equals("")) {
				
				if(this.roomId == null){					
					// Make user busy
					this.userMapper.setBusyState(this.userId, true);
				}			
			
				// Create new chat room for 2 users
				this.streamIn = AUDIOVIDEO + UUID.randomUUID().toString();
				this.streamOut = AUDIOVIDEO + UUID.randomUUID().toString();
				this.roomId = roomMapper.saveRoom(this.userId, this.guestId, streamIn, streamOut, true);
	
				// Add new control post to new chat room
				chatMapper.saveChat(this.roomId, this.userId, START_ROOM + UUID.randomUUID().toString());
	
				// Active users listener
				activeUserListener(this.userId);
				activeUserListener(this.guestId);
				
			} else {
				logger.info("NEXT: No free users to chat!");
				this.message = MSG_NO_USER;
			}
		
		} catch (Exception e) {
			logger.error(e);
			this.message = MSG_PROBLEMS;
		}
	}
	
	/**
	 * Add new post by user to chat.
	 * 
	 * @return
	 */
	public void addSentense() {

		this.message = EMPTY_STRING;
		
		try {
			
			if (this.active == true && this.roomId != null && (this.userPost != null && !this.userPost.equals(""))) {
					
				// Add new post to DB
				chatMapper.saveChat(this.roomId, this.userId.toString(), this.userPost);
				this.userPost = EMPTY_STRING;
	
				// Active users listener
				activeUserListener(this.userId);
				activeUserListener(this.guestId);
				
			} else {
				logger.warn("Dont add new sentence");
				this.message = MSG_ADD_POST_ERROR;				
			}
			
		} catch (Exception e) {
			logger.error(e);
			this.message = MSG_PROBLEMS;
		}
		
	}

	/**
	 * Get all post from room.
	 * 
	 * @return
	 */
	public List<Chat> getAllChats() {

		List<Chat> chats = new ArrayList<Chat>();

		// if user don't open chat room
		if(this.roomId == null){
			
			try {
				
				// Find open room with user
				Room openRoom = this.roomMapper.getOpenRoomByUser(this.userId);
				
				if(openRoom != null){
					this.roomId = openRoom.getId();
					this.guestId = openRoom.getHostId();
					
					// Set video streaming for guest user
					this.streamIn = openRoom.getStreamOut();
					this.streamOut = openRoom.getStreamIn();
					
					this.userMapper.setBusyState(this.userId, true);
				}
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		} 
		
		// If user open chat room
		if(this.roomId != null) {
			
			this.message = EMPTY_STRING;
			
			Session s = new Session(DB_HOST, DB_PORT);
			Database db = s.getDatabase(DB_NAME);
			
			// Get all chat posts by room	
			View allChatsByRoom = new View("/_design/chat/_view/byRoom");
			allChatsByRoom.setKeyString(roomId);
		
			ViewResults result = db.view(allChatsByRoom);			
			
			//TODO: problem with function inside mapper because HttpClient connection is closed
			// ViewResults result = this.chatMapper.getAllChatsByRoomId(this.roomId);
			
			List<Long> chatDateKeys = new ArrayList<Long>();
			HashMap<Long, Chat> chatsMap = new HashMap<Long, Chat>();
			
			try {
				
				for (Document d : result.getResults()) {				
					Document doc = db.getDocument(d.getId());
	
					Chat chat = new Chat();
					
					Document userDoc = db.getDocument(doc.getString("who"));
					
					// Set post owner
					if(userDoc.getId().equals(this.userId)){
						chat.setWho("You");
					} else {
						if(userDoc.getString("username").equals("temp")){
							chat.setWho("Stranger");
						} else {
							chat.setWho(userDoc.getString("username"));
						}
					}
					
					// Set post content
					String post = doc.getString("post");
					if(post.contains(START_ROOM)){
						
						post = "Chat started ...";
						
					} else if(post.contains(STOP_ROOM + this.roomId)){
						post = "User disconected!";
						
						// Make user busy state to false
						this.userMapper.setBusyState(this.userId, false);
						
						this.guestId = null;
						this.roomId = null;
						
						this.streamIn = AUDIOVIDEO + UUID.randomUUID().toString();
						this.streamOut = AUDIOVIDEO + UUID.randomUUID().toString();
					}
					
					chat.setPost(post);
					
					// Getting date from chat
					Long date = Long.parseLong(doc.getString("date"));
					chat.setDate(date);
					
					// add date key used to sort posts
					chatDateKeys.add(date);
					chatsMap.put(date, chat);
				}
				
				// sort all posts in room reverse
				Collections.sort(chatDateKeys, Collections.reverseOrder());
				
				// Adding all posts in reverse order
				for(Long key : chatDateKeys)
					chats.add(chatsMap.get(key));
				
				checkStreaming();
				
			} catch (Exception e) {
				e.printStackTrace();
				this.message = MSG_GET_POSTS_ERROR;
			}
		}
		
		return chats;
	}

	/**
	 * Check user status. If user is logged then use his account.
	 * @return
	 */
	public String getCheckLoggedUser(){
		
		HttpServletRequest req = getFacesRequest();
		
		// check if user is logged (session)
		if(req.getUserPrincipal() != null) {			
			String currentUserId = this.userMapper.getUserIdByLogin(req.getUserPrincipal().getName());
			
			// if user logout and login other user account
			if(!currentUserId.equals(this.userId)){
				logger.info("User change account.");
				
				// stop old 'game'
				stopGame();		
				this.roomId = null;
				
				// change old user to new logged user
				this.userId = currentUserId;
			}
			
			this.tempAccount = false;
			
		} else {			
			// if user come to page first time (create temporary account)
			if(!this.tempAccount) {
				logger.info("Create temporary account.");
				
				// stop old 'game' for old logged user
				if(this.userId != null){
					stopGame();		
					this.roomId = null;
				}
				
				// register new temporary user account
				tempUserRegister();
				this.tempAccount = true;
			}
		}
		
		return EMPTY_STRING;
	}	
	
	/**
	 * Active user listener. Getting from map and call onEvent().
	 * 
	 * @param userId
	 */
	private void activeUserListener(String user){
		
		try {
			
			ListenerManager.getInstance().activeListenerByUserId(user);
			
		} catch (NullPointerException e) {
			logger.error("No user in cache!");
		}
	}

	/**
	 * Getting user IP address if null.
	 * 
	 * @return IP address
	 */
	public String getUserIp() {
		String newIpAddress = null;

		try {

			HttpServletRequest request = getFacesRequest();

			newIpAddress = request.getHeader("X-FORWARDED-FOR");
			if (newIpAddress == null || newIpAddress.equals("")) {
				newIpAddress = request.getRemoteAddr();
			}

			if (newIpAddress.contains(",")) {
				String ips[] = newIpAddress.split(",");
				newIpAddress = ips[ips.length - 1].trim();
			}

		} catch (Exception e) {
			logger.error("GetIpAddress error: " + e);
		}

		return newIpAddress;
	}	
	
	/**
	 * Getting request form faces context.
	 * 
	 * @return HttpServletRequest
	 */
	private static HttpServletRequest getFacesRequest() {

		FacesContext context = FacesContext.getCurrentInstance();

		HttpServletRequest request = (HttpServletRequest) context
				.getExternalContext().getRequest();

		return request;
	}
}
